Skip to content

Phase 2: searchable site and printable QR location stickers - #2

Merged
sr320 merged 1 commit into
phase1-migrate-legacy-inventoryfrom
phase2-search-site
Aug 3, 2026
Merged

Phase 2: searchable site and printable QR location stickers#2
sr320 merged 1 commit into
phase1-migrate-legacy-inventoryfrom
phase2-search-site

Conversation

@sr320

@sr320 sr320 commented Aug 3, 2026

Copy link
Copy Markdown
Member

Makes the inventory findable. Stacked on #1 — base is phase1-migrate-legacy-inventory, and GitHub will retarget this to main automatically when #1 merges. Review #1 first.

Phase 2 deliberately comes before the add-item forms: search value has to land before anyone is asked to enter data, or this goes the way of the spreadsheet.

What you get

  • site/index.html — search UI. Instant search across name, category, notes, owner, vendor, catalog number, and location label, so "fridge door" and "cabinet 3" are queries too. Filters for room / category / status, match highlighting, mobile-first, dark mode.
  • scripts/build_site.py — builds _site/index.html.
  • scripts/make_qr_labels.py — printable QR sticker sheet, one per location, filterable by room and kind.
  • .github/workflows/pages.yml — validate → build → deploy on push to main.

Decisions worth a look

No search library. At 499 items, a precomputed lowercase haystack per item plus a scored linear scan is faster than any index, and there's nothing vendored to keep alive. Every query token must match somewhere; name-prefix scores above name-substring above any-field.

The data is inlined into the HTML, not fetched as a JSON sidecar. That means the built page works over file:// — you can open _site/index.html directly with no local server and no CORS trouble. Cost is a 110 KB page, which is nothing.

QR labels are HTML, not PDF. No PDF toolchain to maintain — open and print. Sticker geometry is CSS variables at the top of the generated file, so it can be nudged to fit real label stock without touching the script.

Location deep links roll up descendants. Scanning the sticker on the -20 freezer shows all 49 items across its shelves and drawers, not the zero items filed directly against the freezer itself. That rollup is the entire point of the QR path — a sticker that showed "0 items" would kill trust in the system immediately.

Two bugs found by loading the page instead of assuming

  • The template placeholder was /*__INVENTORY_DATA__*/null but the build substituted only the comment, leaving const DATA = {...}null;. Syntax error, silently blank page — and no console output to hint at it. The placeholder now swallows the null, and the template stays valid JavaScript either way.
  • segno's default SVG carries width/height but no viewBox, so the CSS sticker size only enlarged the SVG viewport while the QR stayed at intrinsic size in the corner. omitsize=True emits a viewBox instead.

Both were only visible by actually rendering it, which is why the screenshots were worth taking.

Three legacy warts fixed, spotted in the UI

Seeing real rows on screen surfaced data problems that reading CSVs hadn't:

  • Multiple parenthetical dates — the earliest becomes received, the rest are preserved in notes instead of dropped.
  • Unbalanced parentheses. The source itself has 2-Mercaptoethanol (8/6/2018) (opened (8/8/2018). Stripping dates left a name ending mid-parenthesis; the orphaned fragment now moves to notes. Zero unbalanced names remain.
  • Documentation now outranks content wordsDNeasy blood and tissue handbook is office, not a blood sample; sample tubes is a container, not a sample.

items.csv changes in this PR are entirely those three fixes. Counts are unchanged at 499.

Needs one manual step

Settings → Pages → Source = GitHub Actions. The workflow can't enable Pages for the repo itself, so the first deploy will fail until that's set.

Also still pending from #1: branch protection on main requiring Code Owner review, or CODEOWNERS stays documentation rather than a gate.

Verified

Built and loaded in a browser at mobile and desktop widths, light and dark. Confirmed working: search + highlighting (dneasy → 5 hits, prefix matches first), room/category/status filters, #213-F20 rolling up 49 nested items, #209-CAB-10 showing 4, the QR sheet rendering 173 stickers 3-across, and --rooms 213 --kinds drawer narrowing to 60. No console errors. validate.py passes with the one expected warning about room 228 having no locations yet.

🤖 Generated with Claude Code

Phase 2: make the inventory findable before anyone is asked to enter data
into it. Search value has to land first or this goes the way of the
spreadsheet.

build_site.py inlines the data into a single self-contained _site/index.html
rather than fetching a JSON sidecar, so the built page works over file:// --
no local server to preview it, and no CORS surprises.

No search library. At 499 items, a precomputed lowercase haystack per item
plus a scored linear scan is faster than any index would be, and there is
nothing vendored to keep alive. Every query token must match somewhere;
name-prefix scores above name-substring above any-field. Location labels are
part of the haystack, so "fridge door" and "cabinet 3" are queries too.

Location deep links (#209-CAB-01) roll up descendants: scanning the sticker on
the -20 freezer shows all 49 items across its shelves and drawers, not the
zero items filed directly against the freezer itself. That rollup is the whole
point of the QR path.

make_qr_labels.py emits HTML, not PDF, so there is no PDF toolchain to
maintain -- open it and print. Sticker geometry lives in CSS variables at the
top of the generated file so it can be nudged to fit label stock without
touching the script. QR payloads in CI take their base URL from
configure-pages output rather than a hardcoded string.

Two bugs found by actually loading the page instead of assuming:

- The data placeholder was `/*__INVENTORY_DATA__*/null` but only the comment
  was substituted, leaving `const DATA = {...}null;` -- a syntax error that
  silently produced a blank page. The placeholder now swallows the `null`, and
  the template stays valid JavaScript.
- segno's default SVG has width/height but no viewBox, so the CSS sticker size
  only enlarged the viewport while the code stayed at intrinsic size in the
  corner. omitsize=True emits a viewBox instead.

Also tightened three legacy-migration warts that were visible in the UI:
multiple parenthetical dates now keep the earliest as received and preserve
the rest in notes; unbalanced parentheses left by date stripping (and present
in the source, e.g. "2-Mercaptoethanol (8/6/2018) (opened (8/8/2018)") move
their fragment to notes instead of shipping a name ending mid-parenthesis;
and documentation now outranks content words, so "DNeasy blood and tissue
handbook" is office rather than a blood sample, while "sample tubes" is a
container rather than a sample.

Pages deploy needs one manual step this workflow cannot do for itself:
Settings > Pages > Source = GitHub Actions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sr320
sr320 requested a review from kubu4 as a code owner August 3, 2026 18:43
@sr320
sr320 merged commit 84bfdc4 into phase1-migrate-legacy-inventory Aug 3, 2026
2 checks passed
@sr320
sr320 deleted the phase2-search-site branch August 3, 2026 18:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant